export type TabCommunicationServiceOptions = {
  handler?: Window;
  url: string;
  event?: (this: TabCommunicationService, event: any) => void;
  timeout?: number;
};
/** 获取随机 UUID */
export function getUUID() {
  // 检查是否支持 crypto.randomUUID
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
    return crypto.randomUUID();
  }

  // fallback 实现
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    const r = Math.random() * 16 | 0;
    const v = c == 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}
export class TabCommunicationService {
  private readonly handler: Window;
  private readonly origin: string;
  private readonly cbks: { [key: string]: (this: TabCommunicationService, data: any) => any; } = {};
  private readonly id: string = getUUID();
  // 使用 AbortController
  private abortController = new AbortController();
  private isValidated = false;
  private readonly eh: (this: TabCommunicationService, data: any) => void;

  constructor (options: TabCommunicationServiceOptions) {
    this.eh = options.event ?? (() => { });
    this.origin = new URL(options.url).origin;
    this.handler = options.handler ? options.handler : window.open(options.url, "_blank") as Window;
    const timeout = options.timeout && options.timeout > 0 ? options.timeout : 10000;
    const h = setTimeout(() => {
      this.destroy();
      options.event instanceof Function && options.event.call(this, false);
    }, timeout);
    const ch = setTimeout(() => {
      this.handler.postMessage({ type: "server" }, this.origin);
    }, 1000);
    this.cbks.connect = () => {
      delete this.cbks.connect;
      clearTimeout(ch);
    };
    this.cbks[this.id] = (data) => {
      clearTimeout(h);
      options.event instanceof Function && options.event.call(this, data);
    };
    // this.handler.addEventListener("message", this.validate.bind(this), { signal: this.abortController.signal });
    window.addEventListener("message", this.validate.bind(this), { signal: this.abortController.signal });
  }


  send(message: any): Promise<any>;
  send(message: any, cbk: (data: any) => void): void;
  send(message: any, cbk?: (data: any) => void) {
    const uuid = getUUID();
    this.handler.postMessage({ data: message, uuid }, this.origin);
    if (cbk instanceof Function) {
      this.cbks[uuid] = cbk;
    } else {
      return new Promise<any>((resolve) => {
        this.cbks[uuid] = resolve;
      });
    }
  }
  /** 销毁实例
   */
  public destroy() {
    this.abortController.abort();
    Object.keys(this.cbks).forEach(k => {
      delete this.cbks[k];
    });
  }

  private validate(e: MessageEvent<string>) {
    if (this.origin === e.origin) {
      if (e.data === "connect") {
        this.handler.postMessage({ type: "validate", uuid: this.id, }, this.origin);
      } else if (e.data === this.id.replaceAll("-", "")) {
        this.isValidated = true;
        this.handler.postMessage({ type: "connect" }, this.origin); // 设置客户端状态为已连接
      } else if (this.isValidated && e.data === "connected") {
        // 移除旧的监听器
        this.abortController.abort();
        // 创建新的 AbortController 用于 recv 监听器
        this.abortController = new AbortController();
        // 添加新的接收监听器
        window.addEventListener("message", this.recv.bind(this), { signal: this.abortController.signal });
        const callback = this.cbks[this.id];
        if (callback instanceof Function) {
          delete this.cbks[this.id];
          callback.call(this, true);
        }
        if (this.cbks.connect instanceof Function) {
          this.cbks.connect.call(this, true);
        }
      }
    }
  }

  private recv(e: MessageEvent<{ data: any; uuid: string; }>) {
    const { data } = e;
    if (data && data.uuid && this.cbks[data.uuid] instanceof Function) {
      try {
        const f = this.cbks[data.uuid];
        delete this.cbks[data.uuid];
        f.call(this, data.data);
      } catch (_) { }
      return;
    }
    this.eh.call(this, data);
  }
}
export type TabCommunicationClientOptions = (this: TabCommunicationClient, data: any) => any;

export class TabCommunicationClient {
  private readonly _cbk: TabCommunicationClientOptions;
  private readonly cbks: { [key: string]: (this: TabCommunicationClient, data: any) => any; } = {};
  isConnected = false;
  origin = "";
  private abortController = new AbortController();
  private readonly handler: { postMessage(data: any, origin: string): void; };

  constructor (cbk: TabCommunicationClientOptions) {
    this._cbk = cbk;
    window.addEventListener("message", (e: MessageEvent<{ data: any; uuid: string; type?: string; }>) => {
      const { data } = e;
      if (this.isConnected) {
        if (e.origin === this.origin) {
          this._cbk(data.data);
        }
      } else if (data.type === "validate" && data.uuid) {
        this.handler.postMessage(data.uuid.replaceAll("-", ""), e.origin); // 返回验证结果
      } else if (data.type === "connect") {
        this.isConnected = true;
        this.origin = e.origin;
        // 告诉父级页面，子级页面已连接
        this.handler.postMessage("connected", e.origin);
        this.abortController.abort();
        this.abortController = new AbortController();
        window.addEventListener("message", async (e) => {
          if (e.origin === this.origin) {
            let data: any;
            try { data = await this._cbk.call(this, e.data.data); } catch (error) { data = error; }
            this.handler.postMessage({ uuid: e.data.uuid, data }, this.origin);
          }
        }, { signal: this.abortController.signal });
      } else if (data.type === "server") {
        this.handler.postMessage("connect", "*");
        // console.log("向服务器发送服务器验证请求", "延后");
      }
    }, { signal: this.abortController.signal });
    this.handler = window.opener ?? window.top;
    this.handler && this.handler.postMessage("connect", "*");
  }
  send(message: any): Promise<any>;
  send(message: any, cbk: (data: any) => void): void;
  send(message: any, cbk?: (data: any) => void) {
    const uuid = getUUID();
    this.handler.postMessage({ data: message, uuid }, this.origin);
    if (cbk instanceof Function) {
      this.cbks[uuid] = cbk;
    } else {
      return new Promise<any>((resolve) => {
        this.cbks[uuid] = resolve;
      });
    }
  }
}